/** ***************************************************************************
 * CoffeeString.hpp
 * 2012 CoffeeParty Ltd.
 * Date Created: 2012-07-21                                  format YYYY-MM-DD
 * Revision Id: $Id$
 ******************************************************************************
 */

#ifndef _COFFEE_STRING_HPP_
#define _COFFEE_STRING_HPP_

#include "CoffeeCommons.hpp"
#include "CoffeeAllocator.hpp"
#include "CoffeeMath.hpp"

template<typename T, typename TAlloc = CoffeeAllocator<T> >
class CoffeeString
{
public:
    CoffeeString()
    : array(0), allocated(1), used(1)
    {
        array = allocator.allocate(1);
        array[0] = 0x00;
    }

    CoffeeString(const CoffeeString<T, TAlloc>& other)
    : array(0), allocated(0), used(0)
    {
        *this = other;
    }

    template<class B, class A>
    CoffeeString(const CoffeeString<B, A>& other)
    : array(0), allocated(0), used(0)
    {
        *this = other;
    }

    explicit CoffeeString(const double number)
    : array(0), allocated(0), used(0)
    {
        char tmp[0xFF];
        snprintf(tmp, 0xFF, "%0.6f", number);
        *this = tmp;
    }

    explicit CoffeeString(int number)
    : array(0), allocated(0), used(0)
    {
        bool negative = false;
        if (number < 0)
        {
            number *= -1;
            negative = true;
        }

        char tmpbuf[16]={0};
        unsigned char idx = 15;

        if (!number)
        {
            tmpbuf[14] = '0';
            *this = &tmpbuf[14];
            return;
        }

        while(number && idx)
        {
            --idx;
            tmpbuf[idx] = (char)('0' + (number % 10));
            number /= 10;
        }

        if (negative)
        {
            --idx;
            tmpbuf[idx] = '-';
        }

        *this = &tmpbuf[idx];
    }

    explicit CoffeeString(unsigned int number)
    : array(0), allocated(0), used(0)
    {
        char tmpbuf[16]={0};
        unsigned int idx = 15;

        if (!number)
        {
            tmpbuf[14] = '0';
            *this = &tmpbuf[14];
            return;
        }

        while(number && idx)
        {
            --idx;
            tmpbuf[idx] = (char)('0' + (number % 10));
            number /= 10;
        }

        *this = &tmpbuf[idx];
    }

    template<class B>
    CoffeeString(const B* const c, unsigned int length)
    : array(0), allocated(0), used(0)
    {
        if (!c)
        {
            *this = "";
            return;
        }

        allocated = used = length + 1;
        array = allocator.allocate(used);
        for (unsigned int i = 0; i < length; ++i)
            array[i] = (T)c[i];

        array[length] = 0;
    }

    template<class B>
    CoffeeString(const B* const c)
    : array(0), allocated(0), used(0)
    {
        *this = c;
    }

    ~CoffeeString()
    {
        allocator.deallocate(array);
    }

    CoffeeString<T,TAlloc>& operator=(const CoffeeString<T,TAlloc>& other)
    {
        if (this == &other)
            return *this;

        used = other.size() + 1;
        if (used > allocated)
        {
            allocator.deallocate(array);
            allocated = used;
            array = allocator.allocate(used);
        }

        const T* p = other.c_str();
        for (unsigned int i = 0; i < used; ++i, ++p)
            array[i] = *p;

        return *this;
    }

    template <class B, class A>
    CoffeeString<T,TAlloc>& operator=(const CoffeeString<B,A>& other)
    {
        *this = other.c_str();
        return *this;
    }


    template <class B>
    CoffeeString<T,TAlloc>& operator=(const B* const c)
    {
        if (!c)
        {
            if (!array)
            {
                array = allocator.allocate(1);
                allocated = 1;
            }
            used = 1;
            array[0] = 0x0;
            return *this;
        }

        if ((void*)c == (void*)array)
            return *this;

        unsigned int length = 0;
        const B* p = c;
        do
        {
            ++length;
        } while(*p++);

        T* old_array = array;

        used = length;
        if (used > allocated)
        {
            allocated = used;
            array = allocator.allocate(used);
        }

        for (unsigned int i = 0; i < length; ++i)
            array[i] = (T)c[i];

        if (old_array != array)
            allocator.deallocate(old_array);

        return *this;
    }

    CoffeeString<T,TAlloc> operator+(const CoffeeString<T,TAlloc>& other) const
    {
        CoffeeString<T,TAlloc> str(*this);
        str.append(other);

        return str;
    }

    template <class B>
    CoffeeString<T,TAlloc> operator+(const B* const c) const
    {
        CoffeeString<T,TAlloc> str(*this);
        str.append(c);

        return str;
    }

    T& operator[](const unsigned int index)
    {
        try_to_break_if(index >= used);
        return array[index];
    }

    const T& operator[](const unsigned int index) const
    {
        try_to_break_if(index >= used);
        return array[index];
    }

    bool operator==(const T* const str) const
    {
        if (!str)
            return false;

        unsigned int i;
        for (i = 0; array[i] && str[i]; ++i)
            if (array[i] != str[i])
                return false;
        return !array[i] && !str[i];
    }

    bool operator==(const CoffeeString<T,TAlloc>& other) const
    {
        for (unsigned int i = 0; array[i] && other.array[i]; ++i)
            if (array[i] != other.array[i])
                return false;
        return used == other.used;
    }

    bool operator<(const CoffeeString<T,TAlloc>& other) const
    {
        for (unsigned int i = 0; array[i] && other.array[i]; ++i)
        {
            int diff = array[i] - other.array[i];
            if (diff)
                return diff < 0;
        }
        return used < other.used;
    }

    bool operator!=(const T* const str) const
    {
        return !(*this == str);
    }

    bool operator!=(const CoffeeString<T,TAlloc>& other) const
    {
        return !(*this == other);
    }

    unsigned int size() const
    {
        return (used - 1);
    }

    const T* c_str() const
    {
        return array;
    }

    void append(T character)
    {
        if (used + 1 > allocated)
            reallocate(used + 1);

        ++used;

        array[used-2] = character;
        array[used-1] = 0;
    }

    void append(const T* const other)
    {
        if (!other)
            return;

        unsigned int length = 0;
        const T* p = other;
        while(*p)
        {
            ++length;
            ++p;
        }

        if (used + length > allocated)
            reallocate(used + length);

        --used;
        ++length;

        for (unsigned int i = 0; i < length; ++i)
            array[i + used] = *(other + i);

        used += length;
    }

    void append(const CoffeeString<T,TAlloc>& other)
    {
        --used;
        unsigned int length = other.size() + 1;

        if (used + length > allocated)
            reallocate(used + length);

        for (unsigned int i = 0; i < length; ++i)
            array[used + i] = other[i];

        used += length;
    }

    void append(const CoffeeString<T, TAlloc>& other, unsigned int length)
    {
        if (other.size() < length)
        {
            append(other);
            return;
        }

        if (used + length > allocated)
            reallocate(used + length);

        --used;

        for (unsigned int l = 0; l < length; ++l)
            array[l + used] = other[l];
        used += length;
        array[used] = 0;
        ++used;
    }

    void reserve(unsigned int count)
    {
        if (count < allocated)
            return;

        reallocate(count);
    }

    CoffeeString<T,TAlloc>& operator+=(T c)
    {
        append(c);
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const T* const c)
    {
        append(c);
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const CoffeeString<T,TAlloc>& other)
    {
        append(other);
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const int i)
    {
        append(CoffeeString<T,TAlloc>(i));
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const unsigned int i)
    {
        append(CoffeeString<T,TAlloc>(i));
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const long i)
    {
        append(CoffeeString<T,TAlloc>(i));
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const unsigned long& i)
    {
        append(CoffeeString<T,TAlloc>(i));
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const double i)
    {
        append(CoffeeString<T,TAlloc>(i));
        return *this;
    }

    CoffeeString<T,TAlloc>& operator+=(const float i)
    {
        append(CoffeeString<T,TAlloc>(i));
        return *this;
    }

    int findFirst(T c) const
    {
        for (unsigned int i = 0; i < used; ++i)
            if (array[i] == c)
                return i;
        return -1;
    }

    int findNext(T c, unsigned int pos) const
    {
        for (unsigned int i = pos; i < used; ++i)
            if (array[i] == c)
                return i;
        return -1;
    }

    int findLast(T c, int pos = -1) const
    {
        pos = c_clamp((pos < 0) ? (int)(used) - 1 : pos, 0, (int)(used) - 1);
        for (unsigned int i = pos; i >= 0; --i)
            if (array[i] == c)
                return i;
        return -1;
    }

    template <class B>
    int find(const B* const str, const unsigned int start = 0) const
    {
        if (str && *str)
        {
            unsigned int len = 0;

            while (str[len])
                ++len;

            if (len > used - 1)
                return -1;

            for (unsigned int i = start; i < (used - len); ++i)
            {
                unsigned int j=0;

                while(str[j] && array[i + j] == str[j])
                    ++j;

                if (!str[j])
                    return i;
            }
        }
        return -1;
    }

    void replace(T c, T tc)
    {
        for (unsigned int i = 0; i < used; ++i)
            if (array[i] == c)
                array[i] = tc;
    }

    void remove(const CoffeeString<T, TAlloc> word)
    {
        unsigned int size = word.size();
        if (!size)
            return;

        unsigned int pos = 0, found = 0;
        for (unsigned int i = 0; i < used; ++i)
        {
            unsigned int j = 0;
            while (j < size)
            {
                if (array[i + j] != word[j])
                    break;
                ++j;
            }

            if (j == size)
            {
                found += size;
                i += size - 1;
                continue;
            }

            array[pos++] = array[i];
        }

        used -= found;
        array[used - 1] = 0;
    }

    T lastChar() const
    {
        return (used > 1) ? array[used - 2] : 0;
    }

    bool equals_ic(const CoffeeString<T, TAlloc> &other) const
    {
        for (unsigned int i = 0; array[i] && other[i]; ++i)
            if (lowerform(array[i]) != lowerform(other[i]))
                return false;
        return used == other.used;
    }

    bool equals_ss_ic(const CoffeeString<T, TAlloc> &other, const int pos = 0) const
    {
        if ((unsigned int)pos > used)
            return false;

        unsigned int i;
        for (i = 0; array[pos + i] && other[i]; ++i)
            if (lowerform(array[pos + i]) != lowerform(other[i]))
                return false;
        return array[pos + i] == 0 && other[i] == 0;
    }

    CoffeeString<T, TAlloc> substr(unsigned int begin, int length) const
    {
        if ((length <= 0) || (begin >= size()))
            return CoffeeString<T, TAlloc>("");

        if ((length + begin) > size())
            length = size() - begin;

        CoffeeString<T, TAlloc> obj;
        obj.reserve(length + 1);

        for (int i = 0; i < length; ++i)
            obj.array[i] = array[i + begin];

        obj.array[length] = 0;
        obj.used = obj.allocated;

        return obj;
    }

    CoffeeString<T, TAlloc> substring(unsigned int begin, int length) const
    {
        if ((length <= 0) || (begin >= size()))
            return CoffeeString<T, TAlloc>("");

        if ((length + begin) > size())
            length = size() - begin;

        CoffeeString<T, TAlloc> obj;
        obj.reserve(length + 1);

        for (int i = 0; i < length; ++i)
            obj.array[i] = array[i + begin];

        obj.array[length] = 0;
        obj.used = obj.allocated;

        return obj;
    }

    void erase(unsigned int index)
    {
        try_to_break_if(index >= used);
        for (unsigned int i = index + 1; i < used; ++i)
            array[i - 1] = array[i];

        --used;
    }

private:
    void reallocate(unsigned int newsize)
    {
        T* old_array = array;
        array = allocator.allocate(newsize);
        allocated = newsize;

        unsigned int amount = (used < newsize) ? used : newsize;
        for (unsigned int i = 0; i < amount; ++i)
            array[i] = old_array[i];

        if (allocated < used)
            used = allocated;

        allocator.deallocate(old_array);
    }

    T* array;
    TAlloc allocator;
    unsigned int allocated;
    unsigned int used;
};

typedef CoffeeString<char>    c_string;
typedef CoffeeString<wchar_t> c_stringw;

typedef const c_string  c_const_string;
typedef const c_stringw c_const_stringw;

#endif /** _COFFEE_STRING_HPP_ */
